#region MIT
/*
Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008, 2009 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Reflection;
using System.ComponentModel;
using System.Collections.Generic;
using Blue.ResourceManager;
using Green.Logging;


namespace EngineOfEvermore.Common
{
	/// <summary>
	/// special exception, thrown if something goes wrong with the serialization
	/// </summary>
	public class XmlSerializationException : Exception
	{
		public XmlSerializationException( string message ) : base( message ) { }
		public XmlSerializationException( string message, Exception innerException ) :
			base( message, innerException ) { }
	}
	
	public enum SerializationTypes {
		perType,
		perInstance,
		full
	}
	
	/// <summary>
	/// main XmlSerialization class
	/// </summary>
	public class XmlSerialization
	{
		private const string DOCUMENT_ROOT = "EoE";
		private const string TYPE_ATTRIBUTE = "type";
		private const string OBJECT_ELEMENT = "Object";
		private const string VERSION_ATTRIBUTE = "fileversion";
		
		private const string VERSION_STRING = "0.1";
		
		private const string DEFAULT_SCHEMA_PATH = "XmlSchema";
		
		private XDocument _document;
		private XElement _root;
		private SerializationTypes _serType = SerializationTypes.full;
		private string _currentResourceName;
		
		private static Dictionary<Type, Type> _serializers = new Dictionary<Type, Type>();
		private static Dictionary<string, string> _typeInfo = new Dictionary<string, string>();
		private static List<string> _schemaPaths = new List<string>();
		private static XmlSchemaSet _schemaset = null;
		
		private static bool _validationEnabled = true;
		
		
		
		#region Properties
		public XDocument Document 
		{ 
			get {
				return _document; 
			}
			
			set {
				_document = value;
				_root = _document.Element( DOCUMENT_ROOT );
				if ( _root == null ) 
					throw new XmlSerializationException( "given XDocument is not a valid XML document. root element '<" + 
					                                    DOCUMENT_ROOT + ">' was not found" );
				_validateXmlDocument();
			}
		}
		
		
		public XElement ObjectRoot 
		{
			get {
				return _root;
			}
		}
		
		
		//// <value>
		/// with the SerializationType the mode of the serialization process can be changed
		/// 	perType: 	 all properties and objects to be serialized which have the perInstanceAttribute set 
		/// 			 	 will be ignored
		/// 	perInstance: all properties and objects to be serialized which have the perTypeAttribute set
		/// 				 will be ignored
		/// 	full:		 all properties and objects that are supported will be serialized
		/// </value>
		public SerializationTypes SerializationType
		{
			get { return _serType; }
			set { _serType = value; }
		}
		
		
		public string CurrentResourceName 
		{
			get { return _currentResourceName; }
			set { _currentResourceName = value; }
		}
		
		
		public static bool ValidationEnabled 
		{
			get { return _validationEnabled; }
			set { _validationEnabled = value; }
		}
		#endregion
		
		
		
		#region constructor
		public XmlSerialization()
		{
			init();
		}
		
		public XmlSerialization( XDocument document ) 
		{
			Document = document;
		}
		
		
		public XmlSerialization( Stream stream )
		{
			System.IO.TextReader reader = new System.IO.StreamReader( stream );
			_document = XDocument.Load( reader );
			reader.Close();
			_root = _document.Element( DOCUMENT_ROOT );
			_validateXmlDocument();
		}
		
		
		public XmlSerialization( string resourceName, Stream stream ) : this( stream )
		{
			_currentResourceName = resourceName;
		}
		#endregion

		
		
		private void init()
		{
			_serType = SerializationTypes.full;
			_root = new XElement( DOCUMENT_ROOT , new XAttribute( VERSION_ATTRIBUTE, VERSION_STRING ) ); //, new XAttribute( "xmlns", XMLNS ) );
			_document = new XDocument( new XDeclaration( "1.0", "UTF-8", "yes" ), _root );
		}			
		
		

        #region static initialization methods
		/// <summary>
		/// adds a specific serializer class to a type
		/// </summary>
		/// <param name="objType">
		/// A <see cref="Type"/> object for which the serializer should be
		/// </param>
		/// <param name="objSerialzerType">
		/// A <see cref="Type"/> object of the serializer class
		/// </param>
		public static void AddObjectSerializer( Type objType, Type objSerialzerType )
		{
			if ( _serializers.ContainsKey( objType ) ) {
				_serializers.Remove( objType );
			}
			_serializers.Add( objType, objSerialzerType );	
		}
		
		
		
		/// <summary>
		/// adds the assembly quallified name for a object name 
		/// (ex "SceneObject", "EngineOfEvermore.Scene.SceneObject, EngineOfEvermore.Scene")
		/// </summary>
		/// <param name="type">
		/// A <see cref="System.String"/> containing the name of the object
		/// </param>
		/// <param name="assemblyQualifiedName">
		/// A <see cref="System.String"/> containing the assembly quallifed name for the object (see obj.GetType().AssemblyQualifiedName
		/// </param>
		public static void AddTypeInformation( string type, string assemblyQualifiedName ) 
		{
			if ( _typeInfo.ContainsKey( type ) ) {
				_typeInfo.Remove( type );
			}
			_typeInfo.Add( type, assemblyQualifiedName );	
		}
        #endregion
		
		
		
		public void AddObject( Object obj ) 
		{
			getXmlFromObject( obj, _root );
		}
		
		
		
		public int GetObjectCount()
		{
			if ( _root != null && _root.Elements() != null ) {
				return _root.Elements().Count();
			} else {
				return 0;
			}
		}
		
		
		
		public T GetObject<T>( int n ) where T : class
		{
			int count = GetObjectCount();
			if ( count == 0 || n < 0 || n >= count )
				return null;
			Object o = loadObjectFromXml( _root.Elements().Skip( n ).First() );
			if ( o is T )
				return (T) o;
			else
				Log.Error( "deserialized Object is not an instance of expected Type '" + typeof(T).GetType().Name + "'" );
			
			return null;
		}
		
		
		
		public void Save( TextWriter writer ) 
		{
			writer.WriteLine( _document );
		}
		
		
		
		public void Save()
		{
			try {
				using (Stream stream = ResourceManager.CreateResource( _currentResourceName ) ) {
					TextWriter tw = new StreamWriter( stream );
					tw.WriteLine( _document );
					tw.Close();
				}
			} catch( ObjectDisposedException e ) {
				throw new XmlSerializationException( "could not save resource", e );
			} catch( ResourceManagerException e ) {
				throw new XmlSerializationException( "could not save resource", e );
			} catch( IOException e ) {
				throw new XmlSerializationException( "could not save resource", e );
			}
		}
				
		
		public void Save( bool replace )
		{
			try {
				if ( replace && ResourceManager.ResourceExists( _currentResourceName ) ) {
					ResourceManager.DeleteResource( _currentResourceName );
				}
			} catch( ObjectDisposedException e ) {
				throw new XmlSerializationException( "could not delete resource", e );
			} catch( ResourceManagerException e ) {
				throw new XmlSerializationException( "could not delete resource", e );
			}
				
			Save();
		}
		
		
		public void Save( string resourceName )
		{
			_currentResourceName = resourceName;
			Save();
		}
				
		
		public void Save( string resourceName, bool replace )
		{
			_currentResourceName = resourceName;
			Save( replace );
		}
		
		
		
		public void Clear()
		{
			init();
		}
		
		
		
		public void getXmlFromObject(Object obj, XElement xml) {
		
			if (obj == null)
				return;
			
			// if the class has the SerializerIgnore attribute, ignore this object
			if ( Attribute.GetCustomAttribute( obj.GetType(), typeof(SerializerIgnoreAttribute) ) != null )
				return;
			
			// if the class has the PerInstance attribute and we are in perType mode, ignore this object
			if ( Attribute.GetCustomAttribute( obj.GetType(), typeof(PerInstanceAttribute) ) != null && _serType == SerializationTypes.perType )
				return;
			
			// if the class has the PerType attribute and we are in perInstance mode, ignore this object
			if ( Attribute.GetCustomAttribute( obj.GetType(), typeof(PerTypeAttribute) ) != null && _serType == SerializationTypes.perInstance )
				return;
			
			if ( ! _typeInfo.ContainsKey( obj.GetType().Name ) ) 
				Log.Warning( "currently there is no Typeinformation stored for Type '" + obj.GetType().Name + "'. " +
				             "Deserialization will be impossible without this information" );
			
			XElement objelem = new XElement( obj.GetType().Name );
			

			// check if the class has a separate Serializer class
			IXmlObjectSerializer serializer = getObjectSerializerFromObject( obj );
			if ( serializer != null ) {				
				serializer.getXml( objelem, this );
				
///// completely untested ( see loadObjectFromXml method )
//			} else if ( obj is Resource ) {		// TODO: please test me !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//				// only store the resource name 
//				objelem.Value = ((Resource) obj).ResourceName;
			
			} else {
				// serialize properties
				getXmlFromProperties(obj, objelem);	
			}
			
			xml.Add(objelem);
			
		}

		
		
		public T loadObjectFromXml<T>( XElement xml ) where T : class
		{
			Object o = loadObjectFromXml( xml );
			if ( o != null )
				return (T) o;
			
			return null;
		}
		
		
		
		public Object loadObjectFromXml( XElement xml ) {
		
			Object obj = null;
			string typename = xml.Name.LocalName;

			// if we can't get the class and assembly, we can't create a object 
			string objname = "";
			if ( ! _typeInfo.TryGetValue( typename, out objname ) ) {
				throw new XmlSerializationException( "could not find type information for type '" + typename + "'. " +
				                                     "propably you are missing some initialization code?" );
			}
			
			Type objType = null;
			try {  // try to get type 
				objType = Type.GetType( objname, true );
			} catch ( TypeLoadException e ) {
				throw new XmlSerializationException( "could not find object type '" + objname + "'", e );
			}
			
			// if the class has the SerializerIgnore attribute, ignore this object
			if ( Attribute.GetCustomAttribute( objType, typeof(SerializerIgnoreAttribute) ) != null ) {
				Log.Warning( "found Xml-Element for object '" + objType.Name + "' but this object has the " +
				             " SerializeIgnoreAttribute defined. ignoring this Xml-Element" );
				return null;
			}
			
			// if the class has the PerInstance attribute and we are in perType mode, ignore this object
			if ( Attribute.GetCustomAttribute( objType, typeof(PerInstanceAttribute) ) != null && _serType == SerializationTypes.perType) {
				Log.Warning( "found Xml-Element for object '" + objType.Name + "' but this object has the " +
				             " PerInstanceAttribute defined and we are perType mode. ignoring this Xml-Element" );
				return null;
			}
				
			// if the class has the PerType attribute and we are in perInstance mode, ignore this object
			if ( Attribute.GetCustomAttribute( objType, typeof(PerTypeAttribute) ) != null && _serType == SerializationTypes.perInstance) {
				Log.Warning( "found Xml-Element for object '" + objType.Name + "' but this object has the " +
				             " PerTypeAttribute defined and we are perInstance mode. ignoring this Xml-Element" );
				return null;
			}
				
			// check if we can get a serialization class
			IXmlObjectSerializer serializer = getObjectSerializerFromType(objType);
			if ( serializer != null ) {
				obj = serializer.loadFromXml(xml, this);

////// completly untested stuff -> does this really work? (static method call to "getResource" ???)			
//			} else if ( objType == typeof(Resource) ) {  // TODO: please test me!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//				MethodInfo getResource = typeof(ResourceManager).GetMethod("getResource");
//				getResource = getResource.MakeGenericMethod(objType);
//				Object[] args = {xml.Value};
//				obj = (Object) getResource.Invoke(null, args);
				
			} else {
				// class has no serializer, so we have to create the object 
				System.Reflection.ConstructorInfo objConst = objType.GetConstructor ( Type.EmptyTypes );
				if ( objConst != null ) {
					obj = objConst.Invoke ( new Object[0] );
				} else {
					throw new ArgumentException( "could not find constructor for Type '"
					                            + objType.Name + "'" );
				}
					
				loadPropertiesFromXml ( obj, xml );
			}
				
			return obj;
		}
		
		
		
		public void getXmlFromProperties(Object obj, XElement xml) {
		
			foreach (PropertyInfo p in obj.GetType().GetProperties()) {
				// ignore property if it has the SerializeIgnore attribute defined
				if ( Attribute.GetCustomAttribute( p, typeof(SerializerIgnoreAttribute) ) != null )
				    continue;
				
				// ignore property if it has the PerInstance attribute and we are in perType mode
				if ( Attribute.GetCustomAttribute( p, typeof(PerInstanceAttribute) ) != null && _serType == SerializationTypes.perType )
				    continue;
				
				// ignore property if it has the PerType attribute and we are in perInstance mode
				if ( Attribute.GetCustomAttribute( p, typeof(PerTypeAttribute) ) != null && _serType == SerializationTypes.perInstance )
				    continue;
				
				// we serialize only properties that are writeable (we have also to deserialize them)
				if ( p.CanWrite ) {
					XElement prop = new XElement( p.Name );
					TypeConverter tc = TypeDescriptor.GetConverter( p.PropertyType );
	
					// check if the property type has a seperate serializer
					IXmlObjectSerializer serializer = getObjectSerializerFromType( p.PropertyType );
					if ( serializer != null ) {
						serializer.Obj = p.GetValue( obj, null );
						serializer.getXml( prop, this );
						
					} else if ( p.PropertyType == typeof(String) ) {
						// if property is a string, we don't have to convert it
						prop.SetValue( p.GetValue( obj, null ) );

					} else if ( tc != null && p.PropertyType.IsPrimitive ) {
						// if property is a primitive, convert using a converter
						prop.SetValue( tc.ConvertToInvariantString( p.GetValue( obj, null ) ) );
					
					} else if ( p.PropertyType.IsEnum ) {
						
						prop.SetValue( tc.ConvertToInvariantString( p.GetValue( obj, null ) ) );
						
					} else if ( p.PropertyType.IsValueType ) {
						// if property is a value type, convert there properties
						getXmlFromProperties( p.GetValue( obj, null ), prop );
						
					} else if ( p.PropertyType.IsClass ) {
						// if property is a class, try to convert by the object converter
						getXmlFromObject(p.GetValue(obj, null), prop);

					} else if ( tc != null && tc.CanConvertTo( typeof(string) ) ) {
						prop.SetValue( tc.ConvertToInvariantString( p.GetValue( obj, null ) ) );
						
					} else {
						// try if we can get a Typconverter and check if we can convert to a string
						Log.Warning("can not serialize property '" + p.Name + "' from object '" + obj.GetType().FullName
						            + "'. please implement a Serializer for it.");
					}
					
					xml.Add(prop);
				} 
			}
		}
		
		
		
		public Object loadPropertiesFromXml( Object obj, XElement xml ) {
	
			if ( obj == null )
				return null;
			
			foreach ( PropertyInfo p in obj.GetType().GetProperties() ) {
				// ignore property if it has the PerInstance attribute and we are in perType mode
				if ( Attribute.GetCustomAttribute( p, typeof(PerInstanceAttribute) ) != null && _serType == SerializationTypes.perType )
				    continue;
				
				// ignore property if it has the PerType attribute and we are in perInstance mode
				if ( Attribute.GetCustomAttribute( p, typeof(PerTypeAttribute) ) != null && _serType == SerializationTypes.perInstance )
				    continue;
				
				Attribute attr = Attribute.GetCustomAttribute( p, typeof(SerializerIgnoreAttribute) );
				
				if ( p.CanWrite && attr == null ) {
					
					XElement prop = xml.Element( p.Name );
					
					if ( prop != null ) {
						TypeConverter tc = TypeDescriptor.GetConverter( p.PropertyType );
						
						IXmlObjectSerializer serializer = getObjectSerializerFromType( p.PropertyType );
						if ( serializer != null ) {
							p.SetValue( obj, serializer.loadFromXml( prop, this ), null );
						
						} else if ( tc != null && ( p.PropertyType.IsPrimitive || p.PropertyType.IsEnum ) ) {
							p.SetValue( obj, tc.ConvertFromInvariantString( prop.Value ), null );
							
						} else if ( p.PropertyType.IsValueType ) {
							p.SetValue( obj, loadPropertiesFromXml( p.GetValue( obj, null ), prop ), null );
							
						} else if ( tc != null && tc.CanConvertFrom( typeof(string) ) ) { 
							p.SetValue( obj, tc.ConvertFromInvariantString( prop.Value ), null );	
							
						} else if ( p.PropertyType.IsClass && prop.Elements().Count() > 0 ) {
							Object pval = loadObjectFromXml( prop.Elements().First() );
							if ( pval != null ) 
								p.SetValue( obj, pval, null );
							
						} else {
							Log.Warning( "can not convert String to property Type '" + p.PropertyType + "' from property '"
							            + p.Name + "', object '" + obj.GetType().FullName + "'" );	
						}
						
					} else {
						if ( attr != null && attr is SerializerIgnoreAttribute ) {
							SerializerIgnoreAttribute sattr = (SerializerIgnoreAttribute) attr;
							
							if ( sattr.DefaultValue != null ) {
								p.SetValue( obj, sattr.DefaultValue, null );
							}
						} else {
						
							Log.Warning( "could not find property value for property '" + p.Name + "' from object '"
						            + obj.GetType().FullName + "' in XML" );	
						}
					}
				}
			}
			return obj;
		}
		
		

		public static void AddSchemaPath( string resourceName )
		{
			if ( ! _schemaPaths.Contains( resourceName ) ) {
				_schemaPaths.Add( resourceName );
				_schemaset = null;
				_loadSchemaSet();
			}
		}
		
		#region helpermethods
		private IXmlObjectSerializer getObjectSerializerFromType ( Type objType ) 
		{
			Type serializerType = null;
			
			// check if we object type has the XmlObjectSerializer attribute defined
			Attribute attr = Attribute.GetCustomAttribute ( objType, typeof (XmlObjectSerializerAttribute) );
			if ( attr != null ) {
				XmlObjectSerializerAttribute serAttr = (XmlObjectSerializerAttribute) attr;
				
				serializerType = serAttr.Serializer;
				
			} else {
				
				_serializers.TryGetValue( objType, out serializerType ); 
				
			}
			
			if ( serializerType != null ) {
				// check if the defined serializer (we got from the attribute) implements the ISmlObjectSerializer interface
				if ( serializerType is IXmlObjectSerializer ) {
					throw new XmlSerializationException( "specified Serializer class for class " + objType.Name 
					                            + " does not implement the IXmlObjectSerializer Interface" );
				}
				
				// get an empty constructor of the serializer
				ConstructorInfo objConst = serializerType.GetConstructor( Type.EmptyTypes );
				if ( objConst == null )
					throw new XmlSerializationException( "could not find constructor with no arguments for class " + serializerType.Name );
				
				// create the serializer
				IXmlObjectSerializer serializer = (IXmlObjectSerializer) objConst.Invoke( new Object[0] );
				
				return serializer;
			}
			
			return null;
		}	
		
		
		private IXmlObjectSerializer getObjectSerializerFromObject ( Object obj ) {
			IXmlObjectSerializer serializer = getObjectSerializerFromType ( obj.GetType() );
			
			// tell the serializer which object should be serialized
			if ( serializer != null )
				serializer.Obj = obj;
			
			return serializer;
		}
		
		
		private static void _loadSchemaSet() {
			if ( ! ResourceManager.IsDisposed ) {
				
				foreach ( string path in _schemaPaths ) {
					if ( ResourceManager.ResourceDirectoryExists( path ) ) {
						
						foreach ( string res in ResourceManager.FindResources( path, "xsd" ) ) {
							XmlSchemaResource s =  
								ResourceManager.GetResource<XmlSchemaResource>( path + 
								                                               System.IO.Path.DirectorySeparatorChar + 
								                                               res );	
							if ( s != null && s.Schema != null ) {
								if ( _schemaset == null )
									_schemaset = new XmlSchemaSet();
								
								_schemaset.Add( s.Schema );
							}
						}
					} else {
						Log.Error( "Could not find resource path '" + path + "'." );
					}
				}
			} else {
				Log.Error( "ResourceManager is not initialized. Could not load any XMLSchema's." );
			}
		}
		
		
		private void _validateXmlDocument() {
			if ( ! _validationEnabled )
				return;
			
			if ( _schemaPaths.Count() == 0 ) {
				_schemaPaths.Add( DEFAULT_SCHEMA_PATH );
				_schemaset = null;
				_loadSchemaSet();
			}
			
			if ( _document != null ) {
				// with mono 2.0.1, we only get en NotImplementedException :(
//				try {
//					_document.Validate( _schemaset, null ); 	
//				} catch (XmlSchemaValidationException e) {
//					throw new XmlSerializationException( "XML schema validation error.", e );
//				}
				
				// lets do a workaround
				XmlDocument doc = new XmlDocument();
				doc.LoadXml( _document.ToString() );
				doc.Schemas = _schemaset;
				
				try {
					doc.Validate( null );
				} catch ( XmlSchemaValidationException e ) {
					throw new XmlSerializationException( "XML schema validation error.", e );
				}
			}
		}
		
		#endregion
		

	}
	
	
	
	#if ENGINE_OF_EVERMORE_ENABLE_UNIT_TESTS
	/// <summary>
	/// Unit tests for XmlSerialization
	/// </summary>
	namespace Test 
	{
		using NUnit.Framework;
		using System.Drawing;	// for RectangleF
		
		[TestFixture]
		public class TestXmlSerialization
		{
			[SetUp]
			public void SetUp()
			{
				XmlSerialization.AddTypeInformation( typeof(SerializationTestClass).Name, typeof(SerializationTestClass).AssemblyQualifiedName );
				XmlSerialization.AddTypeInformation( typeof(SerializationTestClass2).Name, typeof(SerializationTestClass2).AssemblyQualifiedName );
				XmlSerialization.AddTypeInformation( typeof(SerializationTestClass3).Name, typeof(SerializationTestClass3).AssemblyQualifiedName );
				XmlSerialization.AddTypeInformation( typeof(RectangleF).Name, typeof(RectangleF).AssemblyQualifiedName );
				
				XmlSerialization.AddObjectSerializer( typeof(RectangleF), typeof(RectangleFXmlSerializer) );
				
				XmlSerialization.ValidationEnabled = false;
				
				new LogTextWriter( Console.Out );
			}
			
			[TearDown]
			public void TearDown()
			{
			}
			
			
			
			[Test]
			public void TestSerializationForDifferentTyps()
			{
				SerializationTestClass3 s = new SerializationTestClass3();
				XmlSerialization ser = new XmlSerialization();
				
				s.RectProperty = new RectangleF( 9.9f, 8.9f, 7.9f, 6.9f );
				s.IntProperty = 9;
				s.FloatProperty = -8.9f;
				s.StringProperty = "<some> [string]";
				s.PointProperty = new SerializationTestClass3.MyPoint( 9, 8 );
				s.ClassProperty = new SerializationTestClass2();
				s.ClassProperty.Property = "another string % &";
				s.EnumProperty = SerializationTestClass3.MyEnum.e2;
				
				
				ser.AddObject( s );
				ser.Save( Console.Out );
				
				TextWriter tw = new StringWriter();
				ser.Save( tw );
				
				ser.Clear();
				ser.Document = XDocument.Parse( tw.ToString() );
				
				Assert.AreEqual( 1, ser.GetObjectCount() );
				
				SerializationTestClass3 sn = ser.GetObject<SerializationTestClass3>( 0 );
				Assert.IsNotNull( sn );

				
				Assert.AreEqual( 9.9f, sn.RectProperty.X, "Rect X" );
				Assert.AreEqual( 8.9f, sn.RectProperty.Y, "Rect Y" );
				Assert.AreEqual( 7.9f, sn.RectProperty.Width, "Rect Width" );
				Assert.AreEqual( 6.9f, sn.RectProperty.Height, "Rect Height" );
				Assert.AreEqual( 9, sn.IntProperty, "Int" );
				Assert.AreEqual( -8.9f, sn.FloatProperty, "Float" );
				Assert.AreEqual( "<some> [string]", sn.StringProperty, "String" );
				Assert.AreEqual( 9, sn.PointProperty.X, "Point X" );
				Assert.AreEqual( 8, sn.PointProperty.Y, "Point Y" );
				Assert.AreEqual( "another string % &", sn.ClassProperty.Property, "class property" );
				Assert.AreEqual( SerializationTestClass3.MyEnum.e2, sn.EnumProperty );
			}
			
			
			
			[Test]
			public void TestSerializationWithTypeDictionary()
			{
				SerializationTestClass s1 = new SerializationTestClass();
				
				XmlSerialization ser = new XmlSerialization();
				ser.AddObject( s1 );
				ser.Save ( Console.Out );
				
				SerializationTestClass s2 = ser.GetObject<SerializationTestClass>( 0 );
				
				Assert.AreEqual( s1.Position, s2.Position );
				Assert.AreEqual( s1.Rotation, s2.Rotation );
			}
			
		
			
			[Test]
			public void TestMoreObjectsPerSerializer()
			{
				SerializationTestClass s1 = new SerializationTestClass();
				SerializationTestClass s2 = new SerializationTestClass();
				
				s2.Position = "new position";
				s2.Rotation = "new rotation";
				s2.Space = "new spacing";
				s2.Class2Instance.Property = "new property value";
				
				XmlSerialization ser = new XmlSerialization();
				ser.AddObject( s1 );
				ser.AddObject( s2 );
				
				ser.Save( Console.Out );
				
				Assert.That( ser.GetObjectCount() == 2 );
				
				SerializationTestClass sn1 = ser.GetObject<SerializationTestClass>( 0 );
				Assert.That( s1.Position == sn1.Position );
				Assert.That( s1.Rotation == sn1.Rotation );
				Assert.That( s1.Space == sn1.Space );
				Assert.That( s1.Class2Instance.Property == sn1.Class2Instance.Property );
				
				SerializationTestClass sn2 = ser.GetObject<SerializationTestClass>( 1 );
				Assert.That( s2.Position == sn2.Position );
				Assert.That( s2.Rotation == sn2.Rotation );
				Assert.That( s2.Space == sn2.Space );
				Assert.That( s2.Class2Instance.Property == sn2.Class2Instance.Property );
				
				Assert.IsNull( ser.GetObject<SerializationTestClass>( -1 ) );
				Assert.IsNull( ser.GetObject<SerializationTestClass>( 2 ) );
				Assert.IsNull( ser.GetObject<SerializationTestClass>( 10 ) );
	
			}
		
	
			
			[Test]
			public void TestPerInstanceModeSerialization()
			{
				SerializationTestClass o = new SerializationTestClass();
				XmlSerialization s = new XmlSerialization();
				s.SerializationType = SerializationTypes.perInstance;
				
				s.AddObject( o );
				
				XElement oElem = s.Document.Element( "EoE" ).Element( "SerializationTestClass" );
				
				Assert.IsNotNull( oElem );
				Assert.IsNotNull( oElem.Element( "Position" ) );
				Assert.IsNotNull( oElem.Element( "Class2Instance" ) );
				
				Assert.IsNull( oElem.Element( "Rotation" ) );
				Assert.IsNull( oElem.Element( "Class2Type" ) );
				
				Assert.IsNotNull( oElem.Element( "Space" ) );
				
				s.Save( Console.Out );
			}
			
			
			
			[Test]
			public void TestPerInstanceModeDeserialization()
			{
				XmlSerialization s = new XmlSerialization();
				s.SerializationType = SerializationTypes.perInstance;
				
				s.Document = createSerializationTestClassXml();
				
				Assert.AreEqual( s.GetObjectCount(), 1 );
				
				SerializationTestClass so = s.GetObject<SerializationTestClass>( 0 );
				Assert.IsNotNull( so );
				
				// check if the values are set correctly
				Assert.AreEqual( "xxx", so.Position );
				Assert.AreEqual( "xxx", so.Class2Instance.Property );
					
				// check if these values were not been over written
				Assert.AreNotEqual( "xxx", so.Rotation );
				Assert.AreNotEqual( "xxx", so.Class2Type.Property );
				
				Assert.AreEqual( "xxx", so.Space );
			}
			
			
			
			[Test]
			public void TestPerTypeModeSerialization()
			{
				SerializationTestClass o = new SerializationTestClass();
				XmlSerialization s = new XmlSerialization();
				s.SerializationType = SerializationTypes.perType;
				
				s.AddObject( o );
				
				XElement oElem = s.Document.Element( "EoE" ).Element( "SerializationTestClass" );
				
				Assert.IsNotNull( oElem );
				Assert.IsNull( oElem.Element( "Position" ) );
				Assert.IsNull( oElem.Element( "Class2Instance" ) );
				
				Assert.IsNotNull( oElem.Element( "Rotation" ) );
				Assert.IsNotNull( oElem.Element( "Class2Type" ) );
				
				Assert.IsNotNull( oElem.Element( "Space" ) );
				
				s.Save( Console.Out );
			}
			
			
			
			[Test]
			public void TestPerTypeModeDeserialization()
			{
				XmlSerialization s = new XmlSerialization();
				s.SerializationType = SerializationTypes.perType;
				
				s.Document = createSerializationTestClassXml();
				
				Assert.AreEqual( s.GetObjectCount(), 1 );
				
				SerializationTestClass so = s.GetObject<SerializationTestClass>( 0 );
				Assert.IsNotNull( so );
				
				// check if the values are set correctly
				Assert.AreNotEqual( "xxx", so.Position );
				Assert.AreNotEqual( "xxx", so.Class2Instance.Property );
					
				// check if these values were not been over written
				Assert.AreEqual( "xxx", so.Rotation );
				Assert.AreEqual( "xxx", so.Class2Type.Property );
				
				Assert.AreEqual( "xxx", so.Space );
			}
			
			
			/*
			[Test]
			public void TestXmlSchemaStuff()
			{
				XmlSchemaResource xsd = ResourceManager.GetResource<XmlSchemaResource>( "XmlSchema/test.xsd" );
				Assert.IsNotNull( xsd );
	
				XmlSchema schema = xsd.Schema;
				Assert.IsNotNull( schema );
				
				XDocument doc = new XDocument( new XElement( "SceneObject", 
				                                        //new XAttribute( "xmlns", "http://www.internet.at/SceneObject" ),
				                                        new XElement( "Position", 
				                                                 new XElement( "X", 1 ),
				                                                 new XElement( "Y", 2 )
				                                                 ), 
				                                        new XElement( "ResourceName", "hello world" )
				                                       )
				                              );
				
				doc.Save( Console.Out );
				
				XmlSchemaSet schemaset = new XmlSchemaSet();
				schemaset.Add( schema );
				
				XmlDocument xdoc = new XmlDocument();
				xdoc.LoadXml( doc.ToString() );
				xdoc.Schemas = schemaset;
				
				xdoc.Validate( null );
						
			}*/
			
			
            #region creation methods
			private XDocument createSerializationTestClassXml() {
				XElement elem = new XElement( "SerializationTestClass",
				                             		new XElement( "Position", "xxx" ),				// ok
				                             		new XElement( "Class2Instance",					// ok
				                                          new XElement( "SerializationTestClass2", 
				                                          		new XElement( "Property", "xxx" ) 	// ok
				                                          )
				                                    ),
				                             		
				                             		new XElement( "Rotation", "xxx" ),				// not ok
				                             		new XElement( "Class2Type",
				                             				new XElement( "SerializationTestClass2", // not ok
				                                          		new XElement( "Property", "xxx" )
				                                            )
				                                    ),
				                             		new XElement( "Space", "xxx" )			// ok
				                             );
				
				
				return new XDocument( new XElement( "EoE" , elem ) );
			}
            #endregion
		
			
            #region helper classes	
			internal class SerializationTestClass 
			{
		
				[PerInstance]
				public string Position { get; set; }
				
				[PerType]
				public string Rotation { get; set; }
				
				public string Space { get; set; }
				
				public string NotWriteable { get{ return ""; } }
				
				[PerInstance]
				public SerializationTestClass2 Class2Instance { get; set; }
				
				[PerType]
				public SerializationTestClass2 Class2Type { get; set; }
				
				
				public SerializationTestClass() {
						Position = "position";
						Rotation = "rotation";
						Space = "space";
						Class2Instance = new SerializationTestClass2();
						Class2Type = new SerializationTestClass2();
				}
			}

			
			
			internal class SerializationTestClass2 
			{
			
				public string Property { get; set; }
				
				public SerializationTestClass2() {
						Property = "value";
				}
			}
			
			
			
			internal class SerializationTestClass3
			{
				public struct MyPoint 
				{ 
					public int X { get; set; } 
					public int Y { get; set; } 
					public int Z;
					public MyPoint( int _x, int _y ) : this() {
						X = _x;
						Y = _y;
						Z = 0;
					}
				}
				
				public enum MyEnum {
					e1,
					e2
				}
				
				public RectangleF RectProperty { get; set; }	// implements seperate Serializer
				public int IntProperty { get; set; }	// primitive
				public float FloatProperty { get; set; }	// primitive
				public string StringProperty { get; set; }	// string
				public MyPoint PointProperty { get; set; }	// valueType
				public SerializationTestClass2 ClassProperty { get; set; }	// class
				public MyEnum EnumProperty { get; set; }	// enum

				
				public SerializationTestClass3()
				{
					RectProperty = new RectangleF( 1.1f, 2.2f, -1.2f, -2.1f );
					IntProperty = 3;
					FloatProperty = 4.4f;
					StringProperty = "hello";
					PointProperty = new MyPoint( 5, 6 );
					ClassProperty = new SerializationTestClass2();
					EnumProperty = MyEnum.e1;
				}
			}		
            #endregion
	
		}
	}
    #endif
}
